import java.time.Clock;
import java.util.Calendar;
import java.util.Date;

import static java.util.Calendar.*;

/**
 * Date类
 * Java提供了Date类来处理日期、时间（此处的Date是指java.util包下的Date类，而不是java.sql包下的Date类），Date对象既包含日期，也包含时间。
 * Date类从JDK 1.0起就开始存在了，但正因为它历史悠久，所以它的大部分构造器、方法都已经过时，不再推荐使用了。
 * <p>
 * 构造器
 * ➢ Date()：生成一个代表当前日期时间的Date对象。该构造器在底层调用System.currentTimeMillis()获得long整数作为日期参数。
 * ➢ Date(long date)：根据指定的long型整数来生成一个Date对象。
 * 该构造器的参数表示创建的Date对象和GMT 1970年1月1日00：00：00之间的时间差，以毫秒作为计时单位。
 * <p>
 * 方法：
 * ➢ boolean after(Date when)：测试该日期是否在指定日期when之后。
 * ➢ long getTime()：返回该时间对应的long型整数，即从GMT1970-01-01 00：00：00到该Date对象之间的时间差，以毫秒作为计时单位。
 * ➢ boolean before(Date when)：测试该日期是否在指定日期when之前。
 * ➢ void setTime(long time)：设置该Date对象的时间。
 * <p>
 * 总体来说，Date是一个设计相当糟糕的类，因此Java官方推荐尽量少用Date的构造器和方法。
 * 如果需要对日期、时间进行加减运算，或获取指定时间的年、月、日、时、分、秒信息，可使用Calendar工具类。
 */

class DateTest {
    static void test() {
        Date d1 = new Date();
        // 获取当前时间之后100ms的时间
        Date d2 = new Date(System.currentTimeMillis() + 100);
        System.out.println(d2);
        System.out.println(d1.compareTo(d2));
        System.out.println(d1.before(d2));
    }
}

/**
 * Calendar类
 * 因为Date类在设计上存在一些缺陷，所以Java提供了Calendar类来更好地处理日期和时间。Calendar是一个抽象类，它用于表示日历。
 * <p>
 * Calendar类本身是一个抽象类，它是所有日历类的模板，并提供了一些所有日历通用的方法；
 * 但它本身不能直接实例化，程序只能创建Calendar子类的实例，Java本身提供了一个GregorianCalendar类，一个代表格里高利日历的子类，它代表了通常所说的公历。
 * 当然，也可以创建自己的Calendar子类，然后将它作为Calendar对象使用（这就是多态）。
 * <p>
 * Calendar类是一个抽象类，所以不能使用构造器来创建Calendar对象。
 * 但它提供了几个静态getInstance()方法来获取Calendar对象，这些方法根据TimeZone，Locale类来获取特定的Calendar，
 * 如果不指定TimeZone、Locale，则使用默认的TimeZone、Locale来创建Calendar。
 */
class CalendarTest {
    private static void test1() {
        // 创建一个默认的Calendar对象
        Calendar c = Calendar.getInstance();
        // 从Calendar对象中取出Date对象
        Date date = c.getTime();
        // 通过Date对象获得对应的Calendar对象
        // 因为Calendar/GregorianCalendar没有构造函数可以接收Date对象
        // 所以必须先获得一个Calendar实例，然后调用器setTime()方法
        Calendar c2 = Calendar.getInstance();
        c2.setTime(date);
    }

    private static void test2() {
        Calendar c = Calendar.getInstance();
        System.out.println(c.get(YEAR));    // 年
        System.out.println(c.get(MONTH));    // 月
        System.out.println(c.get(DATE));    // 日
        // 分别设置年月日时分秒
        // 需要指出的是，Calendar.MONTH字段代表月份，月份的起始值不是1，而是0，所以要设置8月时，用7而不是8。
        c.set(2020, 5, 3, 10, 40, 41);
        ;
        System.out.println(c.getTime());    // Wed Jun 03 10:40:41 CST 2020

        // 将Calendar的年 前推1年
        c.add(YEAR, -1);
        System.out.println(c.getTime());    // Mon Jun 03 10:40:41 CST 2019

        // 将Calendar的月 前推8个月
        c.roll(MONTH, -8);
        System.out.println(c.getTime());    // Thu Oct 03 10:40:41 CST 2019
    }

    /**
     * 1.add与roll的区别
     * add(int field, int amount)的功能非常强大，add主要用于改变Calendar的特定字段的值。
     * 如果需要增加某字段的值，则让amount为正数；如果需要减少某字段的值，则让amount为负数即可。
     */
    private static void test3() {
        //  add(int field, int amount)有如下两条规则。
        // ➢ 当被修改的字段超出它允许的范围时，会发生进位，即上一级字段也会增大。例如：
        Calendar cal1 = Calendar.getInstance();
        cal1.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
        cal1.add(MONTH, 6); // 2003-8-23 => 2004-2-23

        // ➢ 如果下一级字段也需要改变，那么该字段会修正到变化最小的值。例如：
        Calendar cal2 = Calendar.getInstance();
        cal2.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
        // 因为进位后月份改为2月，2月没有31日，自动变为29日
        cal2.add(MONTH, 6);  // 2003-8-31 => 2004-2-29

        // roll()的规则与add()的处理规则不同：当被修改的字段超出它允许的范围时，上一级字段不会增大。
        Calendar cal3 = Calendar.getInstance();
        cal3.set(2003, 7, 23, 0, 0, 0); // 2003-8-23
        // MONTH字段"进位"，但YEAR字段并不增加
        cal3.roll(MONTH, 6);    // 2003-8-23 => 2003-2-23

        // 下一级字段的处理规则与add()相似：
        Calendar cal4 = Calendar.getInstance();
        cal4.set(2003, 7, 31, 0, 0, 0); // 2003-8-31
        // YEAR字段不会改变，2003年2月只有28天
        cal4.roll(MONTH, 6);    // 2003-8-31 => 2003-2-28
    }

    /**
     * 2.设置Calendar的容错性调用Calendar对象的set()方法来改变指定时间字段的值时，
     * 有可能传入一个不合法的参数，例如为MONTH字段设置13，这将会导致怎样的后果呢？看如下程序。
     */
    private static void test4() {
        Calendar cal = Calendar.getInstance();
        // 结果是YEAR字段加1，MONTH字段为1（2月）
        cal.set(MONTH, 13);
        System.out.println(cal.getTime());
        // 关闭容错性
        cal.setLenient(false);
        // 导致运行时异常
//        cal.set(MONTH, 13);
        System.out.println(cal.getTime());

        /*
         * Calendar有两种解释日历字段的模式：
         * lenient模式和non-lenient模式。当Calendar处于lenient模式时，每个时间字段可接受超出它允许范围的值；
         * 当Calendar处于non-lenient模式时，如果为某个时间字段设置的值超出了它允许的取值范围，程序将会抛出异常。
         */
    }

    /**
     * 3.set()方法延迟修改
     * set(f, value)方法将日历字段f更改为value，此外它还设置了一个内部成员变量，以指示日历字段f已经被更改。
     * 尽管日历字段f是立即更改的，但该Calendar所代表的时间却不会立即修改，
     * 直到下次调用get()、getTime()、getTimeInMillis()、add() 或roll()时才会重新计算日历的时间。
     * 这被称为set()方法的延迟修改，采用延迟修改的优势是多次调用set()不会触发多次不必要的计算（需要计算出一个代表实际时间的long型整数）。
     * 下面程序演示了set()方法延迟修改的效果。
     */
    private static void test5() {
        Calendar cal = Calendar.getInstance();
        cal.set(2003, 7, 31);
        // 将月份设为9，但是9月31日不存在
        // 如果立即修改，系统将会把cal自动调整到10月1日
        cal.set(MONTH, 8);
        // 下面代码输出10月1日
//        System.out.println(cal.getTime());  // ①
        // 设置DATE字段为5
        cal.set(DATE, 5);   // ②
        System.out.println(cal.getTime());  // ③
        /*
         * 上面程序中创建了代表2003-8-31的Calendar对象，当把这个对象的MONTH字段加1后应该得到2003-10-1（因为9月没有31日），
         * 如果程序在①号代码处输出当前Calendar里的日期，也会看到输出2003-10-1，③号代码处将输出2003-10-5。
         *
         * 如果程序将①处代码注释起来，因为Calendar的set()方法具有延迟修改的特性，即调用set()方法后Calendar实际上并未计算真实的日期，
         * 它只是使用内部成员变量表记录MONTH字段被修改为8，接着程序设置DATE字段值为5，
         * 程序内部再次记录DATE字段为5——就是9月5日，因此看到③处输出2003-9-5。
         */
    }

    static void test() {
        test1();
        test2();
        test3();
        test4();
        test5();
    }
}

/**
 * Java 8专门新增了一个java.time包，该包下包含了如下常用的类。
 * ➢ Clock：该类用于获取指定时区的当前日期、时间。该类可取代System类的currentTimeMillis()方法，而且提供了更多方法来获取当前日期、时间。
 * 该类提供了大量静态方法来获取Clock对象。
 * ➢ Duration：该类代表持续时间。该类可以非常方便地获取一段时间。
 * ➢ Instant：代表一个具体的时刻, 可以精确到纳秒。
 * ➢ LocalDate：该类代表不带时区的日期, 例如2007-12-03。
 * ➢ LocalTime：该类代表不带时区的时间, 例如10：15：30。
 * ➢ LocalDateTime：该类代表不带时区的日期、时间, 例如2007-12-03T10：15：30。
 * ➢ MonthDay：该类仅代表月日, 例如--04-12。
 * ➢ Year：该类仅代表年, 例如2014。
 * ➢ YearMonth：该类仅代表年月, 例如2014-04。
 * ➢ ZonedDateTime：该类代表一个时区化的日期、时间。
 * ➢ ZoneId：该类代表一个时区。
 * ➢ DayOfWeek：这是一个枚举类，定义了周日到周六的枚举值。
 * ➢ Month：这也是一个枚举类，定义了一月到十二月的枚举值。
 * 下面通过一个简单的程序来示范这些类的用法。
 */
class NewDatePackageTest {
    static void test() {
        Clock clock = Clock.systemUTC();
        System.out.println("当前时刻为：" + clock.instant());
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        DateTest.test();
        CalendarTest.test();
        NewDatePackageTest.test();
    }
}
